﻿//-----------------------------------------------------------------------
// <copyright>
//     Copyright (c) Artur Mustafin. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Excel.Expressions.Classes;
using Excel.Expressions.Interfaces;
using Expressions.Compiler.Interfaces;
using Expressions.Compiler;
#if DEBUG
using System.Text;
#endif

namespace Excel.Expressions.Compiler
{
    internal class Function : CellExpression
    {
        private string _name;
        private readonly Func<double> _eval;

        public Function(string name, CellExpression args)
        {
            _name = name;
            Parameter parameter = args as Parameter;
            int functionCount = External.BinaryFunctions.Count((f) => f.Name == name);
            int enumerableCount = External.EnumerableFunctions.Count((f) => f.Name == name);
            int enumerableExpressionFunctionsCount = External.EnumerableExpressionFunctions.Count((f) => f.Name == name);
            if (functionCount == 1)
            {
                IBinaryFunction function = External.BinaryFunctions.First((f) => f.Name == name);
                parameter.Initialize(function.Eval, this);
                _eval = () => Eval(function, GetParameters());
            }
            if (enumerableCount == 1)
            {
                IEnumerableFunction enumerable = External.EnumerableFunctions.First((f) => f.Name == name);
                while (parameter != null)
                {
                    Add(parameter.Expression);
                    parameter = parameter.Next;
                }
                _eval = () => enumerable.Eval(GetParameterValues());
            }
            if (enumerableExpressionFunctionsCount == 1)
            {
                IEnumerableExpressionFunction enumerable = External.EnumerableExpressionFunctions.First((f) => f.Name == name);
                while (parameter != null)
                {
                    Add(parameter.Expression);
                    parameter = parameter.Next;
                }
                _eval = () => enumerable.Eval(GetExpressionValues());
            }
            if (_eval == null)
            {
                throw new NotSupportedException("Initialization of the undifned function is not supported");
            }
        }

        private static double Eval(IBinaryFunction function, IEnumerable<double> values)
        {
            IEnumerator<double> enumerator = values.GetEnumerator();
            enumerator.MoveNext();
            double value = enumerator.Current;
            while (enumerator.MoveNext())
            {
                value = function.Eval(value, enumerator.Current);
            }
            return value;
        }

        private IEnumerable<double> GetParameters()
        {
            IEnumerator<CellExpression> enumerator = Children.GetEnumerator();
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }

        private IEnumerable<double> GetParameterValues()
        {
            foreach (CellExpression expression in GetExpressions())
            {
                yield return expression;
            }
        }

        private IEnumerable<CellExpression> GetExpressionValues()
        {
            foreach (CellExpression expression in GetExpressions())
            {
                yield return expression;
            }
        }

        private IEnumerable<CellExpression> GetExpressions()
        {
            IEnumerator<CellExpression> enumerator = Children.GetEnumerator();
            while (enumerator.MoveNext())
            {
                CellReferenceGroup group = enumerator.Current as CellReferenceGroup;
                if (group != null)
                {
                    foreach (CellReference reference in group.References)
                    {
                        if (reference.HasExpression)
                        {
                            yield return reference;
                        }
                    }
                }
                else
                {
                    yield return enumerator.Current;
                }
            }
        }

#if DEBUG
        private string GetChildren()
        {
            StringBuilder sb = new StringBuilder();
            IEnumerator<CellExpression> enumerator = Children.GetEnumerator();
            if (enumerator.MoveNext())
            {
                sb.Append(enumerator.Current.ToString());
                while (enumerator.MoveNext())
                {
                    sb.AppendFormat(",{0}", enumerator.Current.ToString());
                }
            }
            return sb.ToString();
        }

        public override string ToString()
        {
            return string.Format("{0}({1})", _name, GetChildren());
        }
#endif

        public override double Eval()
        {
            return _eval();
        }
    }
}
